Skip to content

Harden SDK hook context and capability diagnostics - #105

Merged
unbraind merged 8 commits into
mainfrom
sdk-hook-context-capability-guardrails-20260603
Jun 3, 2026
Merged

unbraind merged 8 commits into
mainfrom
sdk-hook-context-capability-guardrails-20260603

Conversation

@unbraind

@unbraind unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Enrich item-related onWrite hook contexts with item_id, item_type, before, after, and changed_fields for create/update/delete/restore/import writes while preserving existing minimal non-item write events.
  • Add structured missing-capability activation traces and extension_capability_missing doctor remediation so package authors can fix manifest/API drift before publishing.
  • Harden package-first dogfood with activation/blocking/missing-capability assertions and add a first-party manifest/API capability guard.
  • Update commander to 15.0.0, refresh managed pm-changelog to 2026.6.3, and regenerate CHANGELOG.md.

Fixes #103.
Fixes #104.

Tracker: pm-e9ut

Verification

  • pnpm build
  • node scripts/run-tests.mjs test -- tests/unit/contracts-command.spec.ts tests/unit/help-runtime.spec.ts tests/unit/config-positional-value.spec.ts tests/unit/item-store.spec.ts tests/unit/extension-command.spec.ts tests/unit/package-manifest.spec.ts
  • node scripts/run-tests.mjs test -- tests/unit/item-store.spec.ts tests/unit/extension-command.spec.ts tests/unit/package-manifest.spec.ts
  • npm run dogfood:package-first
  • pnpm audit --audit-level moderate && pnpm outdated --format json
  • pnpm security:scan
  • pnpm changelog:pm:check

Notes

  • Auto Release run 26870058549 failed before release gates because SENTRY_PERSONAL_ADMIN_TOKEN is not configured for the scheduled push path; this is separate from the SDK/package fixes here.
  • pm-changelog@2026.6.3 is the latest available package and owns the generated changelog path.

Implement pm-e9ut and GitHub #103/#104 by enriching item-related onWrite hook contexts with item identity, before/after snapshots, and changed fields while preserving minimal path/scope/op events for non-item writes.

Add structured activation traces and doctor remediation for missing manifest capabilities, plus first-party manifest/API capability guard coverage and package-first dogfood assertions for activation/blocking/capability failures.

Update commander to 15.0.0, refresh pm-changelog to 2026.6.3, regenerate CHANGELOG.md, and record tracker evidence.

Verification: pnpm build; node scripts/run-tests.mjs test -- tests/unit/contracts-command.spec.ts tests/unit/help-runtime.spec.ts tests/unit/config-positional-value.spec.ts tests/unit/item-store.spec.ts tests/unit/extension-command.spec.ts tests/unit/package-manifest.spec.ts; node scripts/run-tests.mjs test -- tests/unit/item-store.spec.ts tests/unit/extension-command.spec.ts tests/unit/package-manifest.spec.ts; npm run dogfood:package-first; pnpm audit --audit-level moderate && pnpm outdated --format json; pnpm security:scan; pnpm changelog:pm:check.
@unbraind

unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist review

Please review the SDK hook context and manifest capability guardrail changes, especially compatibility of optional onWrite payload fields, package doctor diagnostics, and package-first dogfood gating.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@unbraind, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 8 minutes and 54 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: f951ab19-af7a-4cd8-8ee1-2cf044310570

📥 Commits

Reviewing files that changed from the base of the PR and between 61a2034 and 84b5ef7.

⛔ Files ignored due to path filters (16)
  • .agents/pm/extensions/pm-changelog/dist/cli.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/cli.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/extension.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/extension.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/extension.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/generator.d.ts is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/generator.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/generator.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/generator.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/index.d.ts is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/index.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/index.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/index.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/types.d.ts is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/types.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/types.js.map is excluded by !**/dist/**, !**/*.map
📒 Files selected for processing (23)
  • .agents/pm/extensions/.managed-extensions.json
  • .agents/pm/extensions/pm-changelog/CHANGELOG.md
  • .agents/pm/extensions/pm-changelog/README.md
  • .agents/pm/extensions/pm-changelog/docs/usage.md
  • .agents/pm/extensions/pm-changelog/manifest.json
  • .agents/pm/extensions/pm-changelog/package.json
  • .agents/pm/history/pm-e9ut.jsonl
  • .agents/pm/tasks/pm-e9ut.toon
  • CHANGELOG.md
  • docs/EXTENSIONS.md
  • docs/SDK.md
  • scripts/dogfood-package-first.mjs
  • src/cli/commands/create.ts
  • src/cli/commands/extension.ts
  • src/cli/commands/extension/doctor.ts
  • src/cli/commands/restore.ts
  • src/core/extensions/extension-types.ts
  • src/core/extensions/loader.ts
  • src/core/store/item-store.ts
  • src/sdk/package-import-adapters.ts
  • tests/unit/extension-command.spec.ts
  • tests/unit/item-store.spec.ts
  • tests/unit/package-manifest.spec.ts
📝 Walkthrough

Walkthrough

Enriches the onWrite hook context passed to lifecycle hooks with item identity and change metadata (item_id, item_type, before, after, changed_fields), enabling push-on-write integrations. Simultaneously adds manifest capability validation that surfaces missing manifest declarations via structured activation traces in pm package doctor --detail deep --trace output, with actionable remediation guidance to prevent silent extension failures on publish.

Changes

Hook context enrichment and manifest capability validation

Layer / File(s) Summary
Extension hook and activation error types
src/core/extensions/extension-types.ts
OnWriteHookContext adds optional item_id, item_type, before, after, and changed_fields fields to enrich hook payloads with item identity and write context. ExtensionActivationFailureTrace adds capability and missing_capability fields for diagnostics.
Manifest capability validation and diagnostics
src/core/extensions/loader.ts, src/cli/commands/extension/doctor.ts, src/cli/commands/extension.ts
Loader's assertExtensionCapability now throws structured createRegistrationValidationError with trace details when a register* call lacks the required manifest capability. Doctor command classifies activation failures into extension_capability_missing:<name>:<capability> warnings, includes capability and missing_capability in deep trace output, and generates remediation guidance instructing users to add the missing capability to manifest.json.
Hook context enrichment at mutation sites
src/cli/commands/create.ts, src/cli/commands/restore.ts, src/core/store/item-store.ts, src/sdk/package-import-adapters.ts
Create, restore, mutateItem, and deleteItem operations now pass enriched hook context (item_id, item_type, before/after documents, changed_fields) to runActiveOnWriteHooks for both primary item and history writes. Create computes changedFields before hook execution to make it available during hook invocation.
Documentation, tests, and validation
docs/EXTENSIONS.md, docs/SDK.md, scripts/dogfood-package-first.mjs, tests/unit/extension-command.spec.ts, tests/unit/item-store.spec.ts, tests/unit/package-manifest.spec.ts
SDK and extension docs describe the enriched OnWriteHookContext shape and item-mutation behavior, capability declaration requirements, and doctor diagnostics. Tests validate that missing manifest capabilities produce correct warnings and that hooks receive accurate item metadata and change markers. Dogfood script asserts zero capability-missing warnings in deep doctor runs. New manifest validation test cross-checks first-party extension source code against declared capabilities.

pm-changelog extension version bump

Layer / File(s) Summary
Version and manifest updates
.agents/pm/extensions/.managed-extensions.json, .agents/pm/extensions/pm-changelog/manifest.json, .agents/pm/extensions/pm-changelog/package.json
Version bumped from 2026.6.2 to 2026.6.3; peer dependency on @unbrained/pm-cli raised to >=2026.5.31; package manifest reorganized to use peer dependencies instead of direct dependencies.
Documentation for new opt-in enhancements
.agents/pm/extensions/pm-changelog/CHANGELOG.md, .agents/pm/extensions/pm-changelog/README.md, .agents/pm/extensions/pm-changelog/docs/usage.md
CHANGELOG documents new features (grouping, Conventional Commits, contributors, release limiting, JSON export). README and usage docs provide examples and describe additive flags that preserve backward compatibility.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The PR introduces two coordinated features affecting the extension contract, runtime validation, and multiple mutation call sites. The capability validation logic is straightforward but requires understanding the activation flow, trace payload, and doctor warning classification. The hook context enrichment involves coordinated changes across four mutation entry points (create, restore, mutateItem/deleteItem, import) with careful ordering (early changedFields computation) and document before/after handling. Tests validate both features concretely, but the scope spans type contracts, validation infrastructure, multiple call sites, and documentation.

Possibly related PRs

  • unbraind/pm-cli#78: Updates pm-changelog extension metadata and documentation in the same files, but contains no overlapping core CLI changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main changes: hardening SDK hook context and capability diagnostics for better error handling and richer data.
Description check ✅ Passed The description comprehensively explains the changes, objectives, and verification steps, directly addressing the linked issues #103 and #104.
Linked Issues check ✅ Passed The PR fully addresses both linked issues: #103 is resolved with structured activation traces and doctor remediation for missing capabilities, and #104 is resolved by enriching onWrite context with item_id, item_type, before, after, and changed_fields.
Out of Scope Changes check ✅ Passed All changes are in scope. Updates to commander (15.0.0), pm-changelog (2026.6.3), and CHANGELOG.md are reasonable and directly related to the release workflow, while core changes align with #103 and #104 objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enriches the SDK extension hook context by passing item identity and before/after snapshots to onWrite hooks for item mutations (create, update, delete, restore, and import). It also introduces structured missing-capability activation traces and corresponding doctor diagnostics/remediations to catch manifest capability omissions before publishing. Additionally, the commander dependency is updated to ^15.0.0, and several new opt-in CLI flags are added to pm-changelog (such as --section-by, --conventional, --contributors, --limit, --since-version, and --changelog-json). Review feedback recommends using optional chaining when accessing metadata properties on document objects (such as afterDocument, beforeDocument, restoredDocument, and document) across various command and store files to prevent potential runtime TypeErrors if these documents are undefined or null.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1913 to +1914
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if afterDocument or its metadata is undefined or null (e.g., in testing environments or edge cases), use optional chaining when accessing these properties.

Suggested change
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
item_id: afterDocument?.metadata?.id,
item_type: afterDocument?.metadata?.type,

Comment on lines +1923 to +1924
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if afterDocument or its metadata is undefined or null (e.g., in testing environments or edge cases), use optional chaining when accessing these properties.

Suggested change
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
item_id: afterDocument?.metadata?.id,
item_type: afterDocument?.metadata?.type,

Comment on lines +458 to +459
item_id: restoredDocument.metadata.id,
item_type: restoredDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if restoredDocument or its metadata is undefined or null, use optional chaining when accessing these properties.

Suggested change
item_id: restoredDocument.metadata.id,
item_type: restoredDocument.metadata.type,
item_id: restoredDocument?.metadata?.id,
item_type: restoredDocument?.metadata?.type,

Comment on lines +468 to +469
item_id: restoredDocument.metadata.id,
item_type: restoredDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if restoredDocument or its metadata is undefined or null, use optional chaining when accessing these properties.

Suggested change
item_id: restoredDocument.metadata.id,
item_type: restoredDocument.metadata.type,
item_id: restoredDocument?.metadata?.id,
item_type: restoredDocument?.metadata?.type,

Comment on lines +489 to +490
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if afterDocument or its metadata is undefined or null, use optional chaining when accessing these properties.

Suggested change
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
item_id: afterDocument?.metadata?.id,
item_type: afterDocument?.metadata?.type,

Comment on lines +616 to +617
item_id: beforeDocument.metadata.id,
item_type: beforeDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if beforeDocument or its metadata is undefined or null, use optional chaining when accessing these properties.

Suggested change
item_id: beforeDocument.metadata.id,
item_type: beforeDocument.metadata.type,
item_id: beforeDocument?.metadata?.id,
item_type: beforeDocument?.metadata?.type,

Comment on lines +626 to +627
item_id: beforeDocument.metadata.id,
item_type: beforeDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if beforeDocument or its metadata is undefined or null, use optional chaining when accessing these properties.

Suggested change
item_id: beforeDocument.metadata.id,
item_type: beforeDocument.metadata.type,
item_id: beforeDocument?.metadata?.id,
item_type: beforeDocument?.metadata?.type,

Comment on lines +188 to +189
item_id: document.metadata.id,
item_type: document.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if document or its metadata is undefined or null, use optional chaining when accessing these properties.

Suggested change
item_id: document.metadata.id,
item_type: document.metadata.type,
item_id: document?.metadata?.id,
item_type: document?.metadata?.type,

Comment on lines +198 to +199
item_id: document.metadata.id,
item_type: document.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if document or its metadata is undefined or null, use optional chaining when accessing these properties.

Suggested change
item_id: document.metadata.id,
item_type: document.metadata.type,
item_id: document?.metadata?.id,
item_type: document?.metadata?.type,

Comment thread src/cli/commands/extension/doctor.ts Outdated
Comment on lines +256 to +261
for (const failure of activationFailures) {
const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability;
if (typeof missingCapability === "string" && missingCapability.trim().length > 0) {
warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if failure or failure.trace is undefined or null, use optional chaining when accessing these properties.

Suggested change
for (const failure of activationFailures) {
const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability;
if (typeof missingCapability === "string" && missingCapability.trim().length > 0) {
warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`);
}
}
for (const failure of activationFailures) {
const missingCapability = failure?.trace?.missing_capability ?? failure?.trace?.capability;
if (failure?.name && typeof missingCapability === "string" && missingCapability.trim().length > 0) {
warnings.push("extension_capability_missing:" + failure.name + ":" + missingCapability.trim().toLowerCase());
}
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/unit/package-manifest.spec.ts`:
- Around line 547-548: The current Promise.all mapping swallows all I/O errors
by using .catch(() => ""), so change the catch on the readFile promise (the
async map over sourcePaths using readFile(sourcePath, "utf8")) to only return ""
when the error.code === "ENOENT" and rethrow any other errors; locate the
readFile(...) .catch(...) used inside the Promise.all over sourcePaths and
update the catch handler accordingly to inspect err.code and rethrow non-ENOENT
errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 3db26f3c-dc67-4411-a67b-f0dca41efd11

📥 Commits

Reviewing files that changed from the base of the PR and between 61a2034 and d1d0f6e.

⛔ Files ignored due to path filters (17)
  • .agents/pm/extensions/pm-changelog/dist/cli.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/cli.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/extension.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/extension.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/extension.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/generator.d.ts is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/generator.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/generator.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/generator.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/index.d.ts is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/index.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/index.js is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/index.js.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/types.d.ts is excluded by !**/dist/**
  • .agents/pm/extensions/pm-changelog/dist/types.d.ts.map is excluded by !**/dist/**, !**/*.map
  • .agents/pm/extensions/pm-changelog/dist/types.js.map is excluded by !**/dist/**, !**/*.map
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • .agents/pm/extensions/.managed-extensions.json
  • .agents/pm/extensions/pm-changelog/CHANGELOG.md
  • .agents/pm/extensions/pm-changelog/README.md
  • .agents/pm/extensions/pm-changelog/docs/usage.md
  • .agents/pm/extensions/pm-changelog/manifest.json
  • .agents/pm/extensions/pm-changelog/package.json
  • .agents/pm/history/pm-e9ut.jsonl
  • .agents/pm/tasks/pm-e9ut.toon
  • CHANGELOG.md
  • docs/EXTENSIONS.md
  • docs/SDK.md
  • package.json
  • scripts/dogfood-package-first.mjs
  • src/cli/commands/create.ts
  • src/cli/commands/extension.ts
  • src/cli/commands/extension/doctor.ts
  • src/cli/commands/restore.ts
  • src/core/extensions/extension-types.ts
  • src/core/extensions/loader.ts
  • src/core/store/item-store.ts
  • src/sdk/package-import-adapters.ts
  • tests/unit/extension-command.spec.ts
  • tests/unit/item-store.spec.ts
  • tests/unit/package-manifest.spec.ts

Comment thread tests/unit/package-manifest.spec.ts Resolved

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the pm-changelog extension to version 2026.6.3, introducing several opt-in enhancements such as custom section grouping, Conventional-Commits headings, contributor lists, release windowing, and structured JSON output. It also enriches the onWrite hook context with optional item-aware metadata (such as item ID, type, and before/after snapshots) and improves extension activation diagnostics by tracing and reporting missing manifest capabilities. Additionally, the commander dependency is upgraded to ^15.0.0. The review feedback highlights several robustness improvements, including adding defensive checks and optional chaining to prevent potential runtime TypeError crashes when handling item mutations, create fields, or activation failures, as well as expanding the static capability usage test to scan all source files recursively.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 486 to 504
path: effectiveTargetItemPath,
scope: "project",
op: params.op,
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: mutation.changedFields,
})),
...(await runActiveOnWriteHooks({
path: historyPath,
scope: "project",
op: `${params.op}:history`,
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: mutation.changedFields,
})),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the mutate function passed to mutateItem does not return an object (e.g., if it returns void or undefined after mutating the document in-place), mutation will be undefined. Accessing mutation.changedFields will then throw a TypeError at runtime. Using optional chaining (mutation?.changedFields) prevents this potential crash.

Suggested change
path: effectiveTargetItemPath,
scope: "project",
op: params.op,
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: mutation.changedFields,
})),
...(await runActiveOnWriteHooks({
path: historyPath,
scope: "project",
op: `${params.op}:history`,
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: mutation.changedFields,
})),
path: effectiveTargetItemPath,
scope: "project",
op: params.op,
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: mutation?.changedFields,
})),
...(await runActiveOnWriteHooks({
path: historyPath,
scope: "project",
op: params.op + ":history",
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: mutation?.changedFields,
})),

Comment on lines +1879 to +1882
const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [
...Object.keys(registeredItemFieldValues),
...Object.keys(runtimeCreateFieldValues.values),
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If registeredItemFieldValues or runtimeCreateFieldValues (or runtimeCreateFieldValues.values) is undefined or null, calling Object.keys or accessing .values will throw a TypeError at runtime. Adding nullish coalescing and optional chaining makes this robust against missing or uninitialized field values.

Suggested change
const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [
...Object.keys(registeredItemFieldValues),
...Object.keys(runtimeCreateFieldValues.values),
]);
const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [
...Object.keys(registeredItemFieldValues ?? {}),
...Object.keys(runtimeCreateFieldValues?.values ?? {}),
]);

Comment on lines +252 to +263
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>,
): string[] {
const warnings: string[] = [];
for (const failure of activationFailures) {
const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability;
if (typeof missingCapability === "string" && missingCapability.trim().length > 0) {
warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`);
}
}
return [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If activationFailures is null or undefined (e.g., if the activation result is structured differently or missing the field), iterating over it will throw a TypeError. Adding a default parameter or a defensive check ensures the doctor command remains resilient.

Suggested change
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>,
): string[] {
const warnings: string[] = [];
for (const failure of activationFailures) {
const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability;
if (typeof missingCapability === "string" && missingCapability.trim().length > 0) {
warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`);
}
}
return [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
}
export function classifyDoctorActivationFailureWarnings(
activationFailures?: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>,
): string[] {
const warnings: string[] = [];
if (!activationFailures) return warnings;
for (const failure of activationFailures) {
const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability;
if (typeof missingCapability === "string" && missingCapability.trim().length > 0) {
warnings.push("extension_capability_missing:" + failure.name + ":" + missingCapability.trim().toLowerCase());
}
}
return [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
}

Comment thread tests/unit/package-manifest.spec.ts Outdated
Comment on lines +545 to +548
const sourcePaths = [path.join(extensionDirectory, "index.ts"), path.join(extensionDirectory, "index.js")];
const source = (
await Promise.all(sourcePaths.map(async (sourcePath) => readFile(sourcePath, "utf8").catch(() => "")))
).join("\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Scanning only index.ts and index.js at the root of the extension directory will miss API registrations that are modularized into other source files (e.g., src/commands.ts or src/hooks.ts) and imported. To make this test robust and prevent false negatives, consider scanning all source files recursively using a glob pattern or a recursive directory reader.

Suggested change
const sourcePaths = [path.join(extensionDirectory, "index.ts"), path.join(extensionDirectory, "index.js")];
const source = (
await Promise.all(sourcePaths.map(async (sourcePath) => readFile(sourcePath, "utf8").catch(() => "")))
).join("\n");
const glob = (await import("fast-glob")).default;
const sourcePaths = await glob("**/*.{ts,js}", { cwd: extensionDirectory, absolute: true, ignore: ["**/node_modules/**", "**/dist/**"] });
const source = (
await Promise.all(sourcePaths.map(async (sourcePath) => readFile(sourcePath, "utf8").catch(() => "")))
).join("\n");

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d1d0f6eaef

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread package.json Outdated
"@sentry/node": "^10.56.0",
"@toon-format/toon": "^2.3.0",
"commander": "^14.0.3",
"commander": "^15.0.0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep commander compatible with Node 20

This dependency bump makes installs incompatible with the package's advertised Node support: package.json still declares engines.node as >=20, but the resolved commander@15.0.0 entry in pnpm-lock.yaml requires Node >=22.12.0. In Node 20 environments, package managers running with engine enforcement can reject the install, and users following the documented Node 20 support path will be blocked before any of the SDK/package doctor checks can run.

Useful? React with 👍 / 👎.

item_type: restoredDocument.metadata.type,
before: resolvedCurrentDocument,
after: restoredDocument,
changed_fields: ["restored"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report actual restore fields to onWrite hooks

For restores that change real item metadata/body fields, the new SDK payload tells onWrite hooks only changed_fields: ["restored"], while the command result below computes the actual diff with changedFields(resolvedCurrentDocument, restoredDocument). Sync/audit packages using the new optional payload to mirror exact item changes will miss fields like status, title, or deadline after a restore; pass the same computed changed field list into both restore write hooks instead of the sentinel.

Useful? React with 👍 / 👎.

@unbraind

unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist review
@coderabbitai full review

Follow-up commit a6088f1 addresses the actionable CodeRabbit finding by only swallowing ENOENT when probing optional first-party extension source files. Please re-review the updated PR for any remaining SDK hook context, manifest capability, package doctor, and changelog/dogfood issues.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several opt-in enhancements to the pm-changelog extension, including structured JSON output, custom grouping options, conventional commit headings, contributor lists, and history windowing. It also enriches the onWrite hook context with item-specific metadata and snapshots across creation, mutation, deletion, and restoration paths, while improving capability validation in the SDK. A critical issue was identified in src/cli/commands/create.ts where the undefined variable beforeDocument is referenced, which would cause a runtime ReferenceError during item creation.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 1910 to 1928
path: itemPath,
scope: "project",
op: "create",
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: changedFields,
})),
...(await runActiveOnWriteHooks({
path: historyPath,
scope: "project",
op: "create:history",
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: changedFields,
})),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The variable beforeDocument is referenced on lines 1915 and 1925, but it is not defined anywhere in create.ts. This will cause a runtime ReferenceError when executing the create command with active hooks. Since this is a new item creation, there is no previous state, so before should be set to undefined.

Suggested change
path: itemPath,
scope: "project",
op: "create",
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: changedFields,
})),
...(await runActiveOnWriteHooks({
path: historyPath,
scope: "project",
op: "create:history",
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,
changed_fields: changedFields,
})),
path: itemPath,
scope: "project",
op: "create",
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: undefined,
after: afterDocument,
changed_fields: changedFields,
})),
...(await runActiveOnWriteHooks({
path: historyPath,
scope: "project",
op: "create:history",
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: undefined,
after: afterDocument,
changed_fields: changedFields,
})),

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
🧩 Analysis chain

I'll pull the key changed files to do a thorough re-review.


🏁 Script executed:

#!/bin/bash
echo "=== extension-types.ts ==="
cat src/core/extensions/extension-types.ts

echo ""
echo "=== loader.ts ==="
cat src/core/extensions/loader.ts

echo ""
echo "=== extension/doctor.ts ==="
cat src/cli/commands/extension/doctor.ts

echo ""
echo "=== extension.ts (relevant section) ==="
cat src/cli/commands/extension.ts

echo ""
echo "=== item-store.ts (relevant sections) ==="
cat src/core/store/item-store.ts

echo ""
echo "=== package-import-adapters.ts ==="
cat src/sdk/package-import-adapters.ts

echo ""
echo "=== create.ts ==="
cat src/cli/commands/create.ts

echo ""
echo "=== restore.ts ==="
cat src/cli/commands/restore.ts

echo ""
echo "=== dogfood-package-first.mjs ==="
cat scripts/dogfood-package-first.mjs

echo ""
echo "=== package-manifest.spec.ts ==="
cat tests/unit/package-manifest.spec.ts

echo ""
echo "=== item-store.spec.ts ==="
cat tests/unit/item-store.spec.ts

echo ""
echo "=== extension-command.spec.ts ==="
cat tests/unit/extension-command.spec.ts

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Fetch the PR diff and file list via GitHub API
gh pr view 105 --repo unbraind/pm-cli --json files,title,body,commits | jq '.'

Unrecoverable error: Stream initialization permanently failed: Received unknown message on Sandbox stream


@unbraind: Thanks for the update — the ENOENT-only swallowing fix in a6088f1 is the correct approach. Here is the re-review across all four focus areas plus the commander bump.


✅ What looks good

SDK hook context (OnWriteHookContext)

All five new fields (item_id, item_type, before, after, changed_fields) are optional, so existing hook implementations continue to compile and run without changes — full backward compatibility.

The ordering fix in src/cli/commands/create.ts — computing changedFields before calling runActiveOnWriteHooks instead of after — is correct and important; hooks now receive the data they need when they run.

In src/sdk/package-import-adapters.ts, sharing a single beforeDocument reference across both hook invocations is a clean deduplication.

Manifest capability guardrails

assertExtensionCapability in loader.ts now throws a structured createRegistrationValidationError with a full ExtensionActivationFailureTrace payload instead of a bare TypeError. That's a meaningful improvement for downstream diagnostics.

classifyDoctorActivationFailureWarnings correctly deduplicates and sorts warnings before emitting them, preventing duplicate extension_capability_missing codes for the same extension/capability pair.

Package doctor diagnostics

Running package doctor --detail deep --trace and deriving expectations from the returned details payload (rather than a static summary) is a solid upgrade that will catch real problems in CI.

Dogfood gating

The triple assertion in dogfood-package-first.mjs — zero activation failures, zero blocking failures, no extension_capability_missing codes — is a strong first-party guardrail.


⚠️ Remaining findings

1. Commander 15 — ESM-only and Node.js ≥ 22.12.0 requirement (medium)

Commander 15 is ESM-only; the deprecated commander/esm.mjs export was removed. This is expected to be seamless for ESM consumers, but some CommonJS consumers may hit issues with tooling requiring configuration for ESM-only dependencies.

Commander 15 also requires Node.js v22.12.0 or higher (for require(esm)).

Action needed: Confirm the project's declared engines.node lower bound in package.json is >=22.12.0 (or higher). If the project currently advertises support for older Node versions, the ^15.0.0 pin is a silent runtime breakage for those users. Also confirm no part of the test pipeline or CI matrix still runs on Node 20.

Additionally, Commander 15 changed default option value behavior: only a lone --no-* option now sets the default value to true — if any pm-cli command defines both a positive and a negative variant of the same flag, verify the default is still what you expect.


2. registration_index: -1 sentinel in ExtensionActivationFailureTrace (low)

loader.ts sets registration_index: -1 in the trace payload when assertExtensionCapability fires before any numbered registration entry. -1 is a valid array index sentinel in JavaScript, but nothing in ExtensionActivationFailureTrace documents that -1 means "pre-registration / capability gate" rather than "last element." Consumers parsing the trace field in doctor output may misinterpret it.

Suggestion: Either add a JSDoc comment to registration_index in extension-types.ts noting that -1 signals a pre-registration capability check failure, or add an explicit optional boolean field like pre_registration?: true to the trace type.


3. changed_fields for import operations undocumented (low)

src/sdk/package-import-adapters.ts now passes changed_fields to runActiveOnWriteHooks, but the AI summary does not state what value is used (e.g., ["imported"], an empty array, or something else). docs/SDK.md documents changed_fields for item writes but the import path is not called out.

Suggestion: Add a note to docs/SDK.md under the onWrite context section stating what changed_fields contains for op: "create" writes triggered by pm package import, parallel to the existing documentation for create, update, and delete.


4. package-manifest.spec.ts — coverage limited to index.ts / index.js only (low)

The new first-party capability guard test scans only index.ts and index.js for register* call patterns. Extensions that split their registration logic across multiple source files (e.g., a src/register-commands.ts helper) won't be covered, and the regex scan won't catch dynamically composed or re-exported registration calls.

This is a reasonable best-effort guard, but it should be annotated so future contributors don't rely on it as a complete check:

// NOTE: This is a heuristic text scan of index.ts / index.js only.
// It does not cover multi-file registrations or dynamic patterns.
// Treat failures as definitive but passes as best-effort only.

5. Synthetic changed_fields sentinels not reflected in docs (nit)

restore.ts uses changed_fields: ["restored"] and item-store.ts (delete path) uses changed_fields: ["deleted"]. These are synthetic sentinels, not actual document field names. docs/SDK.md should call these out explicitly so hook authors know to handle them as lifecycle signals rather than schema fields. Currently the doc describes changed_fields as the list of mutated fields, which is accurate for update operations but misleading for restore and delete.


Summary

Area Status
OnWriteHookContext optional fields / backward compat ✅
create.ts ordering fix ✅
loader.ts structured capability error ✅
classifyDoctorActivationFailureWarnings ✅
Dogfood assertions ✅
ENOENT fix (a6088f1) ✅
Commander 15 Node ≥ 22.12.0 + ESM-only ⚠️ verify engines field
registration_index: -1 sentinel ⚠️ document
Import changed_fields value ⚠️ document in SDK.md
package-manifest.spec.ts coverage scope 📝 add comment
Synthetic changed_fields sentinels 📝 add to SDK.md

🧠 Learnings used
Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Use sandbox-safe commands for verification. For documentation-only work, run at least `pnpm build` and a focused link/content check. For broader work, run linked tests, coverage, validation, and release gates.
✅ Actions performed

Full review triggered.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6088f13e1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +122 to +123
if (limit !== undefined && result.length > limit) {
result = result.slice(0, limit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude Unreleased when applying the release limit

When --all-release-tags has any post-latest-tag items, candidateSections starts with the Unreleased bucket, so this slice(0, limit) makes --limit 10 keep Unreleased plus only nine tagged releases. The CLI help and usage docs describe the flag as keeping the most recent N release sections/history releases, while the adjacent comment says Unreleased is always kept, so CI users trimming long histories will silently drop one requested tagged release whenever there is an Unreleased section.

Useful? React with 👍 / 👎.

Comment on lines +1384 to +1390
throw createRegistrationValidationError(
`${method} requires capability '${capability}' in extension manifest capabilities`,
{
method,
registration_index: -1,
capability,
missing_capability: capability,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve missing capability traces through command flag validation

When an extension declares only commands and calls api.registerCommand({ ..., flags: [...] }), the missing schema capability is raised here from inside registerCommand's metadata try block, but that catch wraps the error with a new registerCommand trace that drops missing_capability. In that scenario pm package doctor --detail deep --trace reports a generic activation failure instead of the new extension_capability_missing triage/remediation, so command-definition flags remain an un-diagnosed manifest capability gap.

Useful? React with 👍 / 👎.

Comment on lines +85 to +86
lines.push("### Contributors", "");
lines.push(names.map((name) => `@${name}`).join(", "));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Avoid double-prefixing contributor handles

When --contributors is used and an item's assignee or author is already stored as a handle like @alice, this formatter unconditionally prepends another @, producing @@alice; mixed alice/@alice values also bypass the de-dupe key and can appear twice. Since pm authors often use stable handles, normalizing a leading @ before de-duping/formatting would keep the generated Contributors section valid.

Useful? React with 👍 / 👎.

@unbraind

unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist review
@coderabbitai full review

Follow-up commit 0077666 addresses the latest review pass: commander is reverted to 14.0.3 to preserve package.json Node >=20 and CI Node 20 compatibility, SDK docs now call out lifecycle changed_fields sentinels, registration_index=-1 is documented as a pre-registration trace sentinel, and the static manifest guard is annotated as heuristic. Please re-review for any remaining actionable issues.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
🧩 Analysis chain

I'll pull the current state of the files touched by 0077666 to verify the fixes.


🏁 Script executed:

#!/bin/bash
echo "=== package.json (commander dep) ==="
grep -n "commander" package.json

echo ""
echo "=== docs/SDK.md (onWrite section + changed_fields) ==="
cat docs/SDK.md

echo ""
echo "=== src/core/extensions/extension-types.ts ==="
cat src/core/extensions/extension-types.ts

echo ""
echo "=== tests/unit/package-manifest.spec.ts ==="
cat tests/unit/package-manifest.spec.ts

echo ""
echo "=== src/sdk/package-import-adapters.ts (changed_fields usage) ==="
grep -n "changed_fields\|import" src/sdk/package-import-adapters.ts | head -60

echo ""
echo "=== docs/EXTENSIONS.md (onWrite section) ==="
cat docs/EXTENSIONS.md

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Use GitHub API to read files on the PR branch (head ref)
REPO="unbraind/pm-cli"
BRANCH=$(gh pr view 105 --repo "$REPO" --json headRefName --jq '.headRefName')
echo "Branch: $BRANCH"

for FILE in \
  "package.json" \
  "docs/SDK.md" \
  "src/core/extensions/extension-types.ts" \
  "tests/unit/package-manifest.spec.ts" \
  "src/sdk/package-import-adapters.ts"
do
  echo ""
  echo "====== $FILE ======"
  gh api "repos/$REPO/contents/$FILE?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null || echo "(failed to decode)"
done

Unrecoverable error: Stream initialization permanently failed: Received unknown message on Sandbox stream


@unbraind: The sandbox was unavailable for both script attempts this pass, so I'm working from the PR description, commit message, AI summaries, and the previous review chain. Here is the re-review.


Re-review after commit 0077666

Previously flagged findings — claimed addressed

Finding Claimed fix Verifiable from context?
Commander 15 → Node ≥22 / ESM-only breakage Reverted to ^14.0.3 ✅ PR description confirms
registration_index: -1 sentinel undocumented JSDoc added to ExtensionActivationFailureTrace Cannot verify file; trust commit msg
Import changed_fields value undocumented Note added to docs/SDK.md Cannot verify file; trust commit msg
Synthetic sentinels "restored" / "deleted" Documented in docs/SDK.md Cannot verify file; trust commit msg
package-manifest.spec.ts coverage scope Heuristic comment added Cannot verify file; trust commit msg

All five items were substantive and the described fixes are appropriate. No concerns about the fix approach for any of them.


One remaining low-priority flag (new)

src/sdk/package-import-adapters.ts — shared beforeDocument reference across two hook invocations

The AI summary notes that a single beforeDocument = emptyImportedDocument() is computed once and passed as before to both the item-file hook and the history-file hook. If any onWrite hook implementation mutates the before payload (e.g. context.before.status = "processed"), the second hook invocation will receive the already-mutated object rather than the original empty document.

Since OnWriteHookContext.before is typed as ItemDocument (not Readonly<ItemDocument>), there is no type-level protection against mutation. This is a correctness concern only if hooks mutate payload fields, which is an unusual pattern, but the shared reference means it could produce subtly wrong behavior.

Suggestion (low priority): Shallow-copy the reference at each call site, or freeze the object before passing:

before: Object.freeze({ ...beforeDocument }),

or accept it as-is and document in docs/SDK.md that hook implementations must treat before/after as read-only (similar to React's props contract). The latter is lower overhead and consistent with how SDKs typically frame payload immutability.


Everything else

All other areas — the structured capability error in loader.ts, classifyDoctorActivationFailureWarnings deduplication, dogfood triple assertions, OnWriteHookContext backward compatibility, the new unit tests for item-store and extension-command — remain solid from the previous pass and nothing in the AI summary for 0077666 touches those paths.

The PR is in good shape. The shared-reference issue above is the only new item, and it is low-priority / best addressed with a documentation note if not a code change.


🧠 Learnings used
Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Use sandbox-safe commands for verification. For documentation-only work, run at least `pnpm build` and a focused link/content check. For broader work, run linked tests, coverage, validation, and release gates.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In `unbraind/pm-cli`, the semantic/hybrid search fallback-on-any-error behavior in `src/cli/commands/search.ts` is intentional. The product requirement is that `pm search` must never block an agent. Every degradation emits a machine-readable `search_<mode>_fallback:<reason>:using_keyword_mode` warning (reason ∈ `timeout`|`connection`|`error`) returned to the caller and captured by telemetry. Sentry was explicitly rejected for this path because it is lazily initialized only in error paths and would impose async init latency on the hot (successful) search path.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (with extensions `.toon` and `.jsonl`) are generated and managed exclusively by the `pm` CLI tool itself (via `pm create`, `pm close`, `pm update`, etc.). They are never manually edited. Committing these files alongside the work they track is the established convention in this repo (see PRs `#36`–#39). These files should NOT be flagged with "do not edit directly" or similar warnings in code review.
✅ Actions performed

Full review triggered.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several opt-in enhancements to the pm-changelog extension, including within-release grouping by type, status, or label, Conventional-Commits heading mapping, per-release contributor lists, release window limiting, and structured JSON document output. It also enriches the onWrite hook context with item identity and snapshots, and improves missing manifest capability diagnostics in pm package doctor with structured activation traces. The review feedback points out a potential issue in generator.js where duplicate tags in item.tags can lead to duplicate items under the same tag heading when grouping by label, and suggests deduplicating the tags using a Set before iteration.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

push(titleCase(typeof item.status === "string" && item.status.trim() ? item.status.trim() : "Unknown"), item);
}
else {
const tags = (item.tags ?? []).map((tag) => tag.trim()).filter(Boolean);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If item.tags contains duplicate tags (e.g., ['bug', 'bug']), the current implementation will iterate over the duplicates and push the item multiple times into the same group. Deduplicating the tags using a Set before iteration prevents duplicate items under the same tag heading.

Suggested change
const tags = (item.tags ?? []).map((tag) => tag.trim()).filter(Boolean);
const tags = [...new Set((item.tags ?? []).map((tag) => tag.trim()).filter(Boolean))];

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 007766649a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +122 to +123
if (limit !== undefined && result.length > limit) {
result = result.slice(0, limit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude Unreleased from --limit slicing

When --all-release-tags is used, resolveReleaseTagWindows prepends an Unreleased window, and this code slices the full result array including that sentinel. In the common --all-release-tags --limit 1 case, the output contains only Unreleased and drops the most recent tagged release, even though the new docs/comment say Unreleased is always kept and the limit applies to release sections. Split out the undefined-version sentinel before applying the limit so the newest N actual releases still render.

Useful? React with 👍 / 👎.

Comment on lines +292 to +293
function parseLimit(value) {
const parsed = Number.parseInt(value, 10);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Reject malformed --limit values

For inputs like --limit 1.5 or --limit 1abc, Number.parseInt returns 1, so the new flag silently accepts a malformed value and renders a different release count instead of honoring the documented positive-integer contract. Parse the whole string (for example with Number(...) plus Number.isInteger) so CI/config typos fail fast rather than truncating.

Useful? React with 👍 / 👎.

const names = collectContributors(section.items);
if (names.length > 0) {
lines.push("### Contributors", "");
lines.push(names.map((name) => `@${name}`).join(", "));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Avoid double-prefixing contributor handles

When an item's assignee or author is already stored as a GitHub-style handle such as @alice, enabling --contributors renders it as @@alice here, and alice/@alice are also treated as separate contributors by the de-dupe key. Normalize away one leading @ before de-duping/rendering so existing handle-form values do not produce broken contributor entries.

Useful? React with 👍 / 👎.

@unbraind

unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist review
@coderabbitai full review

Follow-up commit 9705c2a records the package-owned review follow-up only. The latest Gemini finding was verified and fixed upstream in pm-changelog PR #25 (unbraind/pm-changelog#25) under tracked item pmc-an10; this pm-cli commit only documents that evidence in pm-e9ut. Please re-review the final pm-cli PR head for any remaining actionable issues.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
REPO="unbraind/pm-cli"
BRANCH=$(gh pr view 105 --repo "$REPO" --json headRefName --jq '.headRefName')
echo "Branch: $BRANCH"

for FILE in \
  "package.json" \
  "docs/SDK.md" \
  "docs/EXTENSIONS.md" \
  "src/core/extensions/extension-types.ts" \
  "src/core/extensions/loader.ts" \
  "src/cli/commands/extension/doctor.ts" \
  "src/cli/commands/extension.ts" \
  "src/core/store/item-store.ts" \
  "src/sdk/package-import-adapters.ts" \
  "src/cli/commands/create.ts" \
  "src/cli/commands/restore.ts" \
  "scripts/dogfood-package-first.mjs" \
  "tests/unit/package-manifest.spec.ts" \
  "tests/unit/item-store.spec.ts" \
  "tests/unit/extension-command.spec.ts"
do
  echo ""
  echo "====== $FILE ======"
  gh api "repos/$REPO/contents/$FILE?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null || echo "(failed to decode)"
done

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
REPO="unbraind/pm-cli"
BRANCH=$(gh pr view 105 --repo "$REPO" --json headRefName --jq '.headRefName')
echo "Branch: $BRANCH"

for FILE in \
  "package.json" \
  "docs/SDK.md" \
  "src/core/extensions/extension-types.ts" \
  "src/core/extensions/loader.ts" \
  "src/cli/commands/extension/doctor.ts" \
  "src/sdk/package-import-adapters.ts" \
  "src/cli/commands/restore.ts" \
  "scripts/dogfood-package-first.mjs" \
  "tests/unit/package-manifest.spec.ts"
do
  echo ""
  echo "====== $FILE ======"
  gh api "repos/$REPO/contents/$FILE?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null || echo "(failed)"
done

Unrecoverable error: Stream initialization permanently failed: Received unknown message on Sandbox stream


@unbraind: The sandbox was unavailable for file reads this pass, so the assessment is based on the PR description, AI summaries, and the complete review chain. Here is the final pass.


Final re-review after commit 9705c2a

Commit 9705c2a — nothing to flag

This commit is documentation-only: it appends entries to .agents/pm/history/pm-e9ut.jsonl recording upstream evidence from pm-changelog PR #25 / pmc-an10. No code, type signatures, tests, or docs changed in pm-cli itself. Nothing new to flag.


Status of all previously raised findings

Finding Addressed in Status
Commander 15 → Node ≥22 / ESM-only 00776664 (reverted to ^14.0.3) ✅
registration_index: -1 sentinel undocumented 00776664 (JSDoc added) ✅
Import changed_fields value undocumented 00776664 (SDK.md note) ✅
Synthetic "restored" / "deleted" sentinels undocumented 00776664 (SDK.md note) ✅
package-manifest.spec.ts coverage scope 00776664 (heuristic comment) ✅
ENOENT-only swallowing in manifest guard a6088f13 ✅
Shared beforeDocument reference in package-import-adapters.ts Not addressed ⚠️ open (low priority)

One remaining open item

Shared beforeDocument in src/sdk/package-import-adapters.ts (low priority)

A single emptyImportedDocument() result is passed as before to both the item-file and history-file runActiveOnWriteHooks invocations. Because OnWriteHookContext.before is not Readonly<ItemDocument>, a hook that mutates context.before in the first invocation will corrupt the object seen by the second. This is an edge case — hook mutation of payload fields is uncommon — but it is a latent correctness issue for third-party hook implementers.

The fix is either defensive:

before: { ...beforeDocument },   // shallow copy at each call site

or contractual (add a note to docs/SDK.md that before/after must be treated as read-only by hook implementations).

This does not block the PR. Would you like me to open a follow-up issue to track it?


Overall assessment

The PR is in good shape. All six substantive findings from the previous passes are resolved. The one remaining item is low-priority and does not affect correctness for well-behaved hook implementations. The SDK hook context enrichment, structured capability guardrails, doctor diagnostics, and dogfood gating are all solid.

✅ Actions performed

Full review triggered.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several opt-in enhancements to the pm-changelog extension, including within-release grouping (--section-by), Conventional-Commits headings (--conventional), per-release contributor lists (--contributors), history windowing (--limit and --since-version), and structured JSON output (--changelog-json). Additionally, it enriches the SDK's onWrite hook context with item identity and content snapshots, and implements manifest capability guardrails that raise actionable diagnostics (extension_capability_missing) in pm package doctor when registration APIs are called without declaring the required capabilities. Feedback on the changes suggests adding a guard in classifyDoctorActivationFailureWarnings to prevent runtime crashes if activationFailures is null or undefined, using optional chaining (?.) when accessing document.metadata in package-import-adapters.ts to safely handle malformed or missing metadata, and expanding the sourcePaths in the package manifest unit test to scan standard src/ entry points for first-party extensions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/cli/commands/extension/doctor.ts Outdated
Comment on lines +252 to +256
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>,
): string[] {
const warnings: string[] = [];
for (const failure of activationFailures) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (such as TypeError: activationFailures is not iterable) when activationFailures is null or undefined, we should add a guard to classifyDoctorActivationFailureWarnings.

export function classifyDoctorActivationFailureWarnings(
  activationFailures?: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> | null,
): string[] {
  const warnings: string[] = [];
  if (!activationFailures) {
    return warnings;
  }
  for (const failure of activationFailures) {

Comment on lines +188 to +189
item_id: document.metadata.id,
item_type: document.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since document is passed from external package importers, it is safer to use optional chaining (?.) when accessing document.metadata to prevent potential TypeErrors if the imported document is malformed or missing metadata.

Suggested change
item_id: document.metadata.id,
item_type: document.metadata.type,
item_id: document?.metadata?.id,
item_type: document?.metadata?.type,

Comment on lines +198 to +199
item_id: document.metadata.id,
item_type: document.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use optional chaining here as well to safely access document.metadata properties.

Suggested change
item_id: document.metadata.id,
item_type: document.metadata.type,
item_id: document?.metadata?.id,
item_type: document?.metadata?.type,

Comment thread tests/unit/package-manifest.spec.ts Outdated
Comment on lines +545 to +546
// Heuristic guard: catches direct index.ts/index.js registrations, while package doctor remains the runtime check.
const sourcePaths = [path.join(extensionDirectory, "index.ts"), path.join(extensionDirectory, "index.js")];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

First-party extensions (such as pm-changelog itself) typically organize their source code under a src/ directory (e.g., src/extension.ts or src/index.ts). To ensure the heuristic capability alignment check actually scans these source files, we should expand sourcePaths to include standard src/ entry points.

      // Heuristic guard: catches direct index.ts/index.js registrations, while package doctor remains the runtime check.
      const sourcePaths = [
        path.join(extensionDirectory, "index.ts"),
        path.join(extensionDirectory, "index.js"),
        path.join(extensionDirectory, "src", "index.ts"),
        path.join(extensionDirectory, "src", "extension.ts"),
      ];

@unbraind

unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist review
@coderabbitai full review

Follow-up commit 3fc9634 redacts the host-specific path from pm evidence after pnpm security:scan correctly caught it in CI. Local verification now passes the full static job sequence: pnpm version:check && pnpm changelog:pm:check && pnpm security:scan && pnpm quality:static && npm pack --dry-run. Please re-review the final head for remaining actionable issues.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
REPO="unbraind/pm-cli"
BRANCH=$(gh pr view 105 --repo "$REPO" --json headRefName --jq '.headRefName')
echo "Branch: $BRANCH"

echo ""
echo "====== Recent commits on PR ======"
gh pr view 105 --repo "$REPO" --json commits --jq '.commits[-5:] | .[] | {oid: .oid[0:8], message: .messageHeadline}'

echo ""
echo "====== .agents/pm/history/pm-e9ut.jsonl (last 40 lines) ======"
gh api "repos/$REPO/contents/.agents/pm/history/pm-e9ut.jsonl?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null | tail -40

echo ""
echo "====== .agents/pm/tasks/pm-e9ut.toon ======"
gh api "repos/$REPO/contents/.agents/pm/tasks/pm-e9ut.toon?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

echo ""
echo "====== src/sdk/package-import-adapters.ts ======"
gh api "repos/$REPO/contents/src/sdk/package-import-adapters.ts?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

echo ""
echo "====== docs/SDK.md ======"
gh api "repos/$REPO/contents/docs/SDK.md?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

echo ""
echo "====== package.json (commander + engines) ======"
gh api "repos/$REPO/contents/package.json?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null | grep -E '"commander|"node"|"engines"' | head -10

echo ""
echo "====== src/core/extensions/extension-types.ts ======"
gh api "repos/$REPO/contents/src/core/extensions/extension-types.ts?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

echo ""
echo "====== scripts/dogfood-package-first.mjs ======"
gh api "repos/$REPO/contents/scripts/dogfood-package-first.mjs?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

Unrecoverable error: Failed to clone repository


@unbraind: The sandbox was again unavailable for file reads, so this pass is based on the commit message, PR description, AI summaries, and the full review chain.


Final re-review after commit 3fc9634

Commit 3fc9634 — clean, nothing to flag

This commit is a narrow tracker-evidence redaction: a host-specific local filesystem path (captured during local verification runs) was present in .agents/pm/history/pm-e9ut.jsonl and pnpm security:scan correctly caught it in CI before merge. The fix is appropriate. The fact that security:scan covers .agents/pm/ evidence files is a good sign — the gate is working as intended. No code, type signatures, tests, or docs changed.


Status of all findings across the full review chain

Finding Addressed in Status
ENOENT-only swallowing in manifest guard a6088f13 ✅
Commander 15 → Node ≥22 / ESM-only breakage 00776664 ✅
registration_index: -1 sentinel undocumented 00776664 ✅
Import changed_fields value undocumented 00776664 ✅
Synthetic "restored" / "deleted" sentinels undocumented 00776664 ✅
package-manifest.spec.ts heuristic scope 00776664 ✅
Host-specific path in pm evidence 3fc96344 ✅
Shared beforeDocument ref in package-import-adapters.ts — ⚠️ open (low priority)

One remaining low-priority item

The shared emptyImportedDocument() reference passed as before to both runActiveOnWriteHooks invocations in src/sdk/package-import-adapters.ts is the only open item. Because OnWriteHookContext.before is not Readonly<ItemDocument>, a hook that mutates the payload in the first invocation would corrupt what the second invocation sees. This does not affect the current first-party hooks but is a latent hazard for third-party implementers.

Would you like me to open a follow-up GitHub issue to track it? It's clearly out of scope for this PR given everything else is resolved.


Overall

The PR is in excellent shape. All seven substantive findings from the review chain are resolved, and pnpm security:scan is doing its job well. The remaining item is low-priority and does not block merge.


🧠 Learnings used
Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Use sandbox-safe commands for verification. For documentation-only work, run at least `pnpm build` and a focused link/content check. For broader work, run linked tests, coverage, validation, and release gates.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (with extensions `.toon` and `.jsonl`) are generated and managed exclusively by the `pm` CLI tool itself (via `pm create`, `pm close`, `pm update`, etc.). They are never manually edited. Committing these files alongside the work they track is the established convention in this repo (see PRs `#36`–#39). These files should NOT be flagged with "do not edit directly" or similar warnings in code review.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-28T05:46:05.731Z
Learning: In the `unbraind/pm-cli` repository, `.agents/pm/` files (issues, history, epics, extensions) are the source of truth for the pm CLI tracker and are committed to git as the result of pm CLI mutations (`pm close`, `pm comments`, `pm changelog generate`, etc.). They should not be flagged as hand-edited files in code reviews.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-28T06:10:29.890Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (including `.agents/pm/epics/*.toon`, `.agents/pm/extensions/.managed-extensions.json`, `.agents/pm/history/*.jsonl`, and similar) are intentional source-of-truth tracker files for the pm-cli project management system. They are tracked in git by design and should NOT be flagged as files that shouldn't be edited or committed. Review comments suggesting these files should not be modified are false-positives for this repository.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In `unbraind/pm-cli`, the semantic/hybrid search fallback-on-any-error behavior in `src/cli/commands/search.ts` is intentional. The product requirement is that `pm search` must never block an agent. Every degradation emits a machine-readable `search_<mode>_fallback:<reason>:using_keyword_mode` warning (reason ∈ `timeout`|`connection`|`error`) returned to the caller and captured by telemetry. Sentry was explicitly rejected for this path because it is lazily initialized only in error paths and would impose async init latency on the hot (successful) search path.

Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Keep edits scoped to the claimed item. Link changed files with `pm files`, docs with `pm docs`, and runnable verification with `pm test`.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-30T22:17:32.126Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/**` (e.g. `.agents/pm/issues/`, `.agents/pm/chores/`, `.agents/pm/features/`, `.agents/pm/history/`) are git-tracked by design and are created/updated exclusively via the sanctioned `pm` CLI tooling (`pm create`, `pm close`, `pm comments`, etc.). These files are the repository's internal system of record and should NOT be flagged as "do not edit directly" — direct edits via the `pm` CLI are the correct and intended workflow.

Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Every code, docs, or test change must be linked to a `pm` item with files/docs/tests and evidence comments.
✅ Actions performed

Full review triggered.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enriches the onWrite hook context with item identity and snapshots during item mutations (create, update, delete, restore, and import) and updates the extension loader to throw structured registration validation errors when required capabilities are missing from the manifest. It also introduces several opt-in enhancements to pm-changelog, such as structured JSON output, release windowing, contributor lists, and custom section grouping. Feedback on the changes suggests expanding the source paths checked by the first-party manifest capability alignment test to include src/ directories (e.g., src/index.ts and src/extension.ts) to prevent false negatives on extensions like pm-changelog itself.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread tests/unit/package-manifest.spec.ts Outdated
const manifest = JSON.parse(await readFile(manifestPath, "utf8")) as { capabilities?: unknown };
const declaredCapabilities = new Set(Array.isArray(manifest.capabilities) ? manifest.capabilities : []);
// Heuristic guard: catches direct index.ts/index.js registrations, while package doctor remains the runtime check.
const sourcePaths = [path.join(extensionDirectory, "index.ts"), path.join(extensionDirectory, "index.js")];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The heuristic guard currently only checks index.ts and index.js in the root of the extension directory. However, first-party extensions (such as pm-changelog itself) often place their source code in a src/ directory (e.g., src/extension.ts or src/index.ts). As a result, this test will completely miss registration calls in those extensions, leading to false negatives.

Expanding sourcePaths to include common first-party source locations like src/index.ts and src/extension.ts ensures the alignment check is actually executed for these extensions.

      const sourcePaths = [
        path.join(extensionDirectory, "index.ts"),
        path.join(extensionDirectory, "index.js"),
        path.join(extensionDirectory, "src/index.ts"),
        path.join(extensionDirectory, "src/extension.ts"),
      ];

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the pm-changelog extension to version 2026.6.3 and introduces several opt-in enhancements, including within-release grouping, conventional headings, contributor lists, history limiting, and structured JSON output. Additionally, it enriches the SDK's onWrite hook context with optional item-specific metadata during mutations, improves extension activation diagnostics by tracing missing manifest capabilities, adds a static capability alignment test, and reverts commander to 14.0.3 for Node 20 compatibility. The review feedback suggests defensively using optional chaining when accessing metadata properties on document objects across multiple files, and defaulting the activationFailures parameter to an empty array in classifyDoctorActivationFailureWarnings to prevent potential runtime TypeErrors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1913 to +1914
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if afterDocument or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
item_id: afterDocument?.metadata?.id,
item_type: afterDocument?.metadata?.type,

Comment on lines +1923 to +1924
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if afterDocument or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
item_id: afterDocument?.metadata?.id,
item_type: afterDocument?.metadata?.type,

Comment on lines +458 to +459
item_id: restoredDocument.metadata.id,
item_type: restoredDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if restoredDocument or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: restoredDocument.metadata.id,
item_type: restoredDocument.metadata.type,
item_id: restoredDocument?.metadata?.id,
item_type: restoredDocument?.metadata?.type,

Comment on lines +468 to +469
item_id: restoredDocument.metadata.id,
item_type: restoredDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if restoredDocument or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: restoredDocument.metadata.id,
item_type: restoredDocument.metadata.type,
item_id: restoredDocument?.metadata?.id,
item_type: restoredDocument?.metadata?.type,

Comment on lines +489 to +490
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if afterDocument or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
item_id: afterDocument?.metadata?.id,
item_type: afterDocument?.metadata?.type,

Comment on lines +616 to +617
item_id: beforeDocument.metadata.id,
item_type: beforeDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if beforeDocument or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: beforeDocument.metadata.id,
item_type: beforeDocument.metadata.type,
item_id: beforeDocument?.metadata?.id,
item_type: beforeDocument?.metadata?.type,

Comment on lines +626 to +627
item_id: beforeDocument.metadata.id,
item_type: beforeDocument.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if beforeDocument or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: beforeDocument.metadata.id,
item_type: beforeDocument.metadata.type,
item_id: beforeDocument?.metadata?.id,
item_type: beforeDocument?.metadata?.type,

Comment on lines +188 to +189
item_id: document.metadata.id,
item_type: document.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if document or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: document.metadata.id,
item_type: document.metadata.type,
item_id: document?.metadata?.id,
item_type: document?.metadata?.type,

Comment on lines +198 to +199
item_id: document.metadata.id,
item_type: document.metadata.type,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes (TypeErrors) if document or its metadata is undefined, use optional chaining when accessing id and type.

Suggested change
item_id: document.metadata.id,
item_type: document.metadata.type,
item_id: document?.metadata?.id,
item_type: document?.metadata?.type,

Comment on lines +252 to +254
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>,
): string[] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeErrors if activationFailures is undefined or null, default the parameter to an empty array.

Suggested change
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }>,
): string[] {
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [],
): string[] {

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3fc9634459

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

});
}
if (limit !== undefined && result.length > limit) {
result = result.slice(0, limit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude Unreleased from --limit slicing

When --all-release-tags produces a non-empty Unreleased bucket, resolveReleaseTagWindows prepends it before tagged releases, but this slice applies --limit to the whole array. As a result, --all-release-tags --limit 1 returns only Unreleased and drops the newest tagged release, and --limit N yields only N-1 actual releases whenever Unreleased is present, contrary to the new docs/contract that limit keeps the newest N releases while Unreleased is always kept. Keep the Unreleased section outside the counted slice.

Useful? React with 👍 / 👎.

item_id: afterDocument.metadata.id,
item_type: afterDocument.metadata.type,
before: beforeDocument,
after: afterDocument,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Report the post-override item snapshot to write hooks

In repositories with an item_store_write service override that changes contents or uses skip_write, this new hook context still publishes the pre-override afterDocument even though line 459 writes effectiveSerializedAfter (or writes nothing). A sync/audit extension consuming the new after snapshot can therefore mirror an item state that was not actually written at effectiveTargetItemPath; parse the effective contents for the hook snapshot or omit after when the override makes it unreliable.

Useful? React with 👍 / 👎.

// OPT-IN (`--changelog-json`): structured document; never writes a file.
if (booleanOption(ctx.options, "changelog-json", "changelogJson")) {
const document = buildChangelogDocument(generationOptions);
return { document, format: "json", item_count: document.item_count };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Emit raw JSON for the extension changelog-json flag

For pm changelog generate --changelog-json without the global --json flag, returning this object still goes through pm's default TOON renderer; the format: "json" field is just payload data and does not switch output formatting. The advertised extension command therefore prints a TOON wrapper around document instead of a JSON changelog document, unlike the standalone CLI path. Return native JSON/mark the command result for JSON output, or require and document the global --json flag.

Useful? React with 👍 / 👎.

@unbraind

unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist review
@coderabbitai full review

Follow-up commit 4555cc8 addresses the latest actionable review suggestions: the doctor activation failure classifier now defaults to an empty array, and the first-party manifest capability heuristic also checks common src/index.* and src/extension.* entry files. Local verification passed focused tests, build, and the full static job sequence. Please re-review the final head for remaining actionable issues.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
REPO="unbraind/pm-cli"
BRANCH=$(gh pr view 105 --repo "$REPO" --json headRefName --jq '.headRefName')
echo "Branch: $BRANCH"

echo ""
echo "====== Recent commits on PR ======"
gh pr view 105 --repo "$REPO" --json commits --jq '.commits[-6:] | .[] | {oid: .oid[0:8], message: .messageHeadline}'

echo ""
echo "====== src/cli/commands/extension/doctor.ts ======"
gh api "repos/$REPO/contents/src/cli/commands/extension/doctor.ts?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

echo ""
echo "====== tests/unit/package-manifest.spec.ts ======"
gh api "repos/$REPO/contents/tests/unit/package-manifest.spec.ts?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

echo ""
echo "====== src/sdk/package-import-adapters.ts ======"
gh api "repos/$REPO/contents/src/sdk/package-import-adapters.ts?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

echo ""
echo "====== src/core/extensions/extension-types.ts ======"
gh api "repos/$REPO/contents/src/core/extensions/extension-types.ts?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

echo ""
echo "====== docs/SDK.md ======"
gh api "repos/$REPO/contents/docs/SDK.md?ref=$BRANCH" --jq '.content' | base64 -d 2>/dev/null

Unrecoverable error: Failed to clone repository


🏁 Script executed:

#!/bin/bash
# Try GitHub API directly without cloning
REPO="unbraind/pm-cli"

# Get PR head SHA directly
HEAD_SHA=$(gh api "repos/$REPO/pulls/105" --jq '.head.sha')
echo "Head SHA: $HEAD_SHA"

echo ""
echo "====== Recent commits ======"
gh api "repos/$REPO/pulls/105/commits" --jq '.[-6:] | .[] | {sha: .sha[0:8], message: .commit.message | split("\n")[0]}'

echo ""
echo "====== src/cli/commands/extension/doctor.ts ======"
gh api "repos/$REPO/contents/src/cli/commands/extension/doctor.ts?ref=$HEAD_SHA" --jq '.content' | base64 -d

echo ""
echo "====== tests/unit/package-manifest.spec.ts ======"
gh api "repos/$REPO/contents/tests/unit/package-manifest.spec.ts?ref=$HEAD_SHA" --jq '.content' | base64 -d

echo ""
echo "====== src/sdk/package-import-adapters.ts ======"
gh api "repos/$REPO/contents/src/sdk/package-import-adapters.ts?ref=$HEAD_SHA" --jq '.content' | base64 -d

Unrecoverable error: Stream initialization permanently failed: Received unknown message on Sandbox stream


@unbraind: The sandbox was again unavailable for file reads this pass, so the assessment is based on the commit description, AI summaries, and the complete review chain. Here is the re-review.


Re-review after commit 4555cc8

Claimed fix 1 — classifyDoctorActivationFailureWarnings defaults to []

This is the right defensive pattern. If activationResult.failed is absent or undefined (e.g. an extension loader that predates the failures field), passing it straight to the classifier would either throw or produce unexpected output. Defaulting the parameter to [] is consistent with how sibling helpers in the doctor path are typically written. ✅

Claimed fix 2 — Heuristic extended to src/index.* / src/extension.*

This directly addresses the coverage-scope concern from the previous pass. Extensions that keep their registration entrypoint under a src/ directory (a common layout for TypeScript extensions with a dist/ output) are now included in the first-party capability guard scan. Combined with the existing heuristic annotation comment (added in 00776664), the test now communicates both its scope and its limits clearly. ✅


Status of all findings across the full review chain

Finding Addressed in Status
ENOENT-only swallowing in manifest guard a6088f13 ✅
Commander 15 → Node ≥22 / ESM-only breakage 00776664 ✅
registration_index: -1 sentinel undocumented 00776664 ✅
Import changed_fields value undocumented 00776664 ✅
Synthetic "restored" / "deleted" sentinels undocumented 00776664 ✅
package-manifest.spec.ts heuristic scope (comment) 00776664 ✅
Host-specific path in pm evidence 3fc96344 ✅
classifyDoctorActivationFailureWarnings empty-array default 4555cc8b ✅
Heuristic extended to src/index.* / src/extension.* 4555cc8b ✅
Shared beforeDocument ref in package-import-adapters.ts — ⚠️ open (low priority)

One remaining low-priority item

The shared emptyImportedDocument() reference passed as before to both runActiveOnWriteHooks invocations in src/sdk/package-import-adapters.ts remains the only open item. A well-behaved hook must not mutate context.before, but OnWriteHookContext.before is not Readonly<ItemDocument> so there is no type-level protection.

This is clearly out of scope for the current PR. Would you like me to open a follow-up GitHub issue to track it?


Overall

All nine actionable findings from the review chain are resolved. The PR is in excellent shape and ready to merge from a review standpoint.


🧠 Learnings used
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In `unbraind/pm-cli`, the semantic/hybrid search fallback-on-any-error behavior in `src/cli/commands/search.ts` is intentional. The product requirement is that `pm search` must never block an agent. Every degradation emits a machine-readable `search_<mode>_fallback:<reason>:using_keyword_mode` warning (reason ∈ `timeout`|`connection`|`error`) returned to the caller and captured by telemetry. Sentry was explicitly rejected for this path because it is lazily initialized only in error paths and would impose async init latency on the hot (successful) search path.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (with extensions `.toon` and `.jsonl`) are generated and managed exclusively by the `pm` CLI tool itself (via `pm create`, `pm close`, `pm update`, etc.). They are never manually edited. Committing these files alongside the work they track is the established convention in this repo (see PRs `#36`–#39). These files should NOT be flagged with "do not edit directly" or similar warnings in code review.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-22T21:06:50.101Z
Learning: In the pm-cli project, `history-redact`, `restore`, and `history-repair` all follow the same intentional read-compute-lock-write pattern: read history + item document outside the lock → compute the rewritten content → acquire the per-item lock → atomic write with rollback. Tightening the read inside the lock is acknowledged as a future hardening item but is intentionally deferred so the locking model stays consistent across all three sibling commands.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-28T05:46:05.731Z
Learning: In the `unbraind/pm-cli` repository, `.agents/pm/` files (issues, history, epics, extensions) are the source of truth for the pm CLI tracker and are committed to git as the result of pm CLI mutations (`pm close`, `pm comments`, `pm changelog generate`, etc.). They should not be flagged as hand-edited files in code reviews.

Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Use sandbox-safe commands for verification. For documentation-only work, run at least `pnpm build` and a focused link/content check. For broader work, run linked tests, coverage, validation, and release gates.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-28T06:10:29.890Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (including `.agents/pm/epics/*.toon`, `.agents/pm/extensions/.managed-extensions.json`, `.agents/pm/history/*.jsonl`, and similar) are intentional source-of-truth tracker files for the pm-cli project management system. They are tracked in git by design and should NOT be flagged as files that shouldn't be edited or committed. Review comments suggesting these files should not be modified are false-positives for this repository.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-30T22:17:32.126Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/**` (e.g. `.agents/pm/issues/`, `.agents/pm/chores/`, `.agents/pm/features/`, `.agents/pm/history/`) are git-tracked by design and are created/updated exclusively via the sanctioned `pm` CLI tooling (`pm create`, `pm close`, `pm comments`, etc.). These files are the repository's internal system of record and should NOT be flagged as "do not edit directly" — direct edits via the `pm` CLI are the correct and intended workflow.
✅ Actions performed

Full review triggered.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enriches the onWrite hook context with item identity, snapshots, and changed fields across item creation, mutation, deletion, restoration, and import. It also introduces structured validation errors and doctor diagnostics (extension_capability_missing) when an extension registers a capability not declared in its manifest. The review feedback suggests defensively checking that activationFailures is an array in classifyDoctorActivationFailureWarnings to prevent potential runtime errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +252 to +255
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [],
): string[] {
const warnings: string[] = [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime errors (such as TypeError: activationFailures is not iterable) if activationFailures is explicitly passed as null or a non-array value, we should defensively verify that it is an array before iterating over it.

export function classifyDoctorActivationFailureWarnings(
  activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [],
): string[] {
  if (!Array.isArray(activationFailures)) {
    return [];
  }
  const warnings: string[] = [];

@unbraind

unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist review
@coderabbitai full review

Follow-up commit 6a84d6a addresses the latest Gemini defensive request by guarding classifyDoctorActivationFailureWarnings with Array.isArray before iteration. Verification passed: focused extension-command test, pnpm build, and pnpm security:scan. Please re-review the final head for remaining actionable issues.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026 •

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

REPO="unbraind/pm-cli"
PR="105"

HEAD_SHA="$(gh api "repos/$REPO/pulls/$PR" --jq '.head.sha')"
HEAD_REF="$(gh api "repos/$REPO/pulls/$PR" --jq '.head.ref')"
echo "PR head ref: $HEAD_REF"
echo "PR head sha: $HEAD_SHA"

echo ""
echo "====== Recent PR commits ======"
gh api "repos/$REPO/pulls/$PR/commits" \
  --jq '.[-8:] | .[] | {sha: .sha[0:8], message: (.commit.message | split("\n")[0])}'

echo ""
echo "====== Latest commit files/patches (6a84d6af if present) ======"
gh api "repos/$REPO/commits/6a84d6af" \
  --jq '.files[]? | "FILE: \(.filename)\nSTATUS: \(.status)\nPATCH:\n\(.patch // "(no patch)")\n---"'

fetch_file() {
  local file="$1"
  echo ""
  echo "====== $file ======"
  gh api "repos/$REPO/contents/$file?ref=$HEAD_SHA" --jq '.content' | base64 -d
}

fetch_file "src/cli/commands/extension/doctor.ts"
fetch_file "src/cli/commands/extension.ts"
fetch_file "tests/unit/extension-command.spec.ts"
fetch_file "tests/unit/package-manifest.spec.ts"
fetch_file "src/sdk/package-import-adapters.ts"
fetch_file "src/core/extensions/extension-types.ts"
fetch_file "docs/SDK.md"
fetch_file "package.json"

Length of output: 269207



@unbraind: Re-reviewed the final head at 6a84d6af.

The latest change in src/cli/commands/extension/doctor.ts looks good:

const failures = Array.isArray(activationFailures) ? activationFailures : [];

This preserves the normal behavior for valid activationResult.failed arrays, safely treats malformed/non-array inputs as empty, and keeps the existing dedupe/sort behavior for extension_capability_missing:<name>:<capability> warnings.

Also verified from the provided output:

  • The latest commit only changes the defensive classifier guard plus pm tracker evidence.
  • The .agents/pm/ updates are expected for this repo’s pm-managed tracker workflow; I used the stored repo learning for that and did not flag them.
  • Missing-capability doctor coverage is present in tests/unit/extension-command.spec.ts.
  • The first-party manifest capability heuristic now scans the expanded common entrypoint set and only swallows ENOENT.

No new actionable issues found in the final pm-cli head. The only previously mentioned item is still the low-priority shared beforeDocument reference in import hooks, but it remains non-blocking and suitable for a follow-up if desired.


🧠 Learnings used
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In `unbraind/pm-cli`, the semantic/hybrid search fallback-on-any-error behavior in `src/cli/commands/search.ts` is intentional. The product requirement is that `pm search` must never block an agent. Every degradation emits a machine-readable `search_<mode>_fallback:<reason>:using_keyword_mode` warning (reason ∈ `timeout`|`connection`|`error`) returned to the caller and captured by telemetry. Sentry was explicitly rejected for this path because it is lazily initialized only in error paths and would impose async init latency on the hot (successful) search path.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (with extensions `.toon` and `.jsonl`) are generated and managed exclusively by the `pm` CLI tool itself (via `pm create`, `pm close`, `pm update`, etc.). They are never manually edited. Committing these files alongside the work they track is the established convention in this repo (see PRs `#36`–#39). These files should NOT be flagged with "do not edit directly" or similar warnings in code review.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-22T21:06:50.101Z
Learning: In the pm-cli project, `history-redact`, `restore`, and `history-repair` all follow the same intentional read-compute-lock-write pattern: read history + item document outside the lock → compute the rewritten content → acquire the per-item lock → atomic write with rollback. Tightening the read inside the lock is acknowledged as a future hardening item but is intentionally deferred so the locking model stays consistent across all three sibling commands.

Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Use sandbox-safe commands for verification. For documentation-only work, run at least `pnpm build` and a focused link/content check. For broader work, run linked tests, coverage, validation, and release gates.
✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several opt-in enhancements to the pm-changelog extension and CLI, including structured JSON output (--changelog-json), alternative release grouping options (--section-by), Conventional-Commits headings (--conventional), contributor lists (--contributors), and history windowing (--limit and --since-version). Additionally, the pm-cli core was updated to enrich the onWrite hook context with item identity and snapshots, and structured traces were added for missing manifest capabilities during activation. The review feedback highlights two potential safety improvements: adding optional chaining when accessing runtimeCreateFieldValues.values in create.ts to prevent a TypeError, and defensively verifying that failure and failure.name are valid in doctor.ts to avoid runtime crashes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1879 to +1882
const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [
...Object.keys(registeredItemFieldValues),
...Object.keys(runtimeCreateFieldValues.values),
]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If runtimeCreateFieldValues is undefined or null, accessing runtimeCreateFieldValues.values will throw a TypeError and crash the command. Consider using optional chaining to safely access values.

Suggested change
const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [
...Object.keys(registeredItemFieldValues),
...Object.keys(runtimeCreateFieldValues.values),
]);
const changedFields = buildChangedFields(frontMatter, body, explicitUnsetKeys, [
...Object.keys(registeredItemFieldValues),
...(runtimeCreateFieldValues?.values ? Object.keys(runtimeCreateFieldValues.values) : []),
]);

Comment on lines +252 to +264
export function classifyDoctorActivationFailureWarnings(
activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [],
): string[] {
const failures = Array.isArray(activationFailures) ? activationFailures : [];
const warnings: string[] = [];
for (const failure of failures) {
const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability;
if (typeof missingCapability === "string" && missingCapability.trim().length > 0) {
warnings.push(`extension_capability_missing:${failure.name}:${missingCapability.trim().toLowerCase()}`);
}
}
return [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime crashes in the doctor command when processing malformed or incomplete activation failures, defensively verify that failure is defined and failure.name is a valid string before accessing its properties.

export function classifyDoctorActivationFailureWarnings(
  activationFailures: Array<{ name: string; trace?: { missing_capability?: string; capability?: string } }> = [],
): string[] {
  const failures = Array.isArray(activationFailures) ? activationFailures : [];
  const warnings: string[] = [];
  for (const failure of failures) {
    if (!failure || typeof failure.name !== "string") {
      continue;
    }
    const missingCapability = failure.trace?.missing_capability ?? failure.trace?.capability;
    if (typeof missingCapability === "string" && missingCapability.trim().length > 0) {
      warnings.push("extension_capability_missing:" + failure.name + ":" + missingCapability.trim().toLowerCase());
    }
  }
  return [...new Set(warnings)].sort((left, right) => left.localeCompare(right));
}

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a84d6af65

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +122 to +123
if (limit !== undefined && result.length > limit) {
result = result.slice(0, limit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Unreleased outside changelog limits

When --all-release-tags --limit N is used and the leading Unreleased section has items, this slices the full section list, so --limit 1 returns only Unreleased and drops the most recent released section. The flag and nearby comment say the limit applies to release-window sections while keeping Unreleased, so users trying to keep pending notes plus the latest N releases will generate an incomplete changelog.

Useful? React with 👍 / 👎.

Comment on lines +1384 to +1390
throw createRegistrationValidationError(
`${method} requires capability '${capability}' in extension manifest capabilities`,
{
method,
registration_index: -1,
capability,
missing_capability: capability,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve nested missing-capability traces

When an extension declares commands but omits schema and uses registerCommand({ ..., flags: [...] }), this missing-capability error is thrown from inside the command metadata validation block and then rewrapped by registerCommand's catch without missing_capability. In that scenario doctor still reports an activation failure, but the new extension_capability_missing triage warning and --trace fields are lost, so the actionable diagnostic added here does not work for inline command flags.

Useful? React with 👍 / 👎.

Comment on lines +124 to +126
if (booleanOption(ctx.options, "changelog-json", "changelogJson")) {
const document = buildChangelogDocument(generationOptions);
return { document, format: "json", item_count: document.item_count };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Emit real JSON for the extension changelog-json flag

For the pm extension command, pm changelog generate --changelog-json returns a normal object, so the default pm output path renders it as TOON unless the caller also knows to pass the global --json. The standalone CLI writes JSON directly, and the docs advertise the same --changelog-json flag for downstream tooling, so redirecting this extension command produces a non-JSON changelog document.

Useful? React with 👍 / 👎.

const names = collectContributors(section.items);
if (names.length > 0) {
lines.push("### Contributors", "");
lines.push(names.map((name) => `@${name}`).join(", "));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Avoid double-prefixing contributor handles

When --contributors is enabled and an item's assignee or author is already stored as a handle such as @alice, this rendering unconditionally prepends another @, producing @@alice in the changelog. That makes contributor sections wrong for the common handle format; normalize the collected name or only add the prefix when it is absent.

Useful? React with 👍 / 👎.

@unbraind

unbraind commented Jun 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review
/gemini review
@gemini-code-assist review
@coderabbitai full review

Follow-up commit 84b5ef7 addresses the latest Gemini defensive suggestions by tolerating absent runtimeCreateFieldValues.values and skipping malformed activation failure entries before reading failure.name. Verification passed focused tests, pnpm build, and pnpm security:scan. Please re-review the final head for remaining actionable issues.

@coderabbitai

coderabbitai Bot commented Jun 3, 2026 •

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

REPO="unbraind/pm-cli"
PR="105"

echo "====== PR head ======"
if command -v gh >/dev/null 2>&1; then
  HEAD_SHA="$(gh api "repos/$REPO/pulls/$PR" --jq '.head.sha')"
  HEAD_REF="$(gh api "repos/$REPO/pulls/$PR" --jq '.head.ref')"
  echo "Head ref: $HEAD_REF"
  echo "Head sha: $HEAD_SHA"

  echo ""
  echo "====== Recent PR commits ======"
  gh api "repos/$REPO/pulls/$PR/commits" \
    --jq '.[-10:] | .[] | {sha: .sha[0:8], message: (.commit.message | split("\n")[0])}'

  echo ""
  echo "====== Latest commit patch: 84b5ef70 ======"
  gh api "repos/$REPO/commits/84b5ef70" \
    --jq '.files[]? | "FILE: \(.filename)\nSTATUS: \(.status)\nPATCH:\n\(.patch // "(no patch)")\n---"' || true
fi

echo ""
echo "====== Repository files available? ======"
pwd
fd -H 'doctor\.ts|extension\.ts|create\.ts|package-manifest\.spec\.ts|extension-command\.spec\.ts|item-store\.spec\.ts|package-import-adapters\.ts|extension-types\.ts|SDK\.md|package\.json' . | sort

echo ""
echo "====== Search: classifyDoctorActivationFailureWarnings ======"
rg -n -C 8 'classifyDoctorActivationFailureWarnings|extension_capability_missing|missing_capability|activationFailures|failure\.name' .

echo ""
echo "====== Search: runtimeCreateFieldValues ======"
rg -n -C 8 'runtimeCreateFieldValues|values' .

echo ""
echo "====== Relevant files from checkout, if present ======"
for file in \
  "src/cli/commands/extension/doctor.ts" \
  "src/cli/commands/extension.ts" \
  "src/cli/commands/create.ts" \
  "tests/unit/extension-command.spec.ts" \
  "tests/unit/package-manifest.spec.ts" \
  "src/sdk/package-import-adapters.ts" \
  "src/core/extensions/extension-types.ts" \
  "docs/SDK.md" \
  "package.json"
do
  if [ -f "$file" ]; then
    echo ""
    echo "====== $file ======"
    sed -n '1,260p' "$file"
  fi
done

Length of output: 277536



@unbraind Re-reviewed the final head at 84b5ef70.

The latest defensive changes look correct:

  • src/cli/commands/create.ts now consistently treats absent runtimeCreateFieldValues.values as {} in:

    • unset filtering
    • frontmatter assembly
    • changed_fields assembly

    That preserves existing behavior when values are present and avoids a hard crash when the runtime create field collector returns an incomplete object.

  • src/cli/commands/extension/doctor.ts now skips malformed activation failure entries before reading failure.name, on top of the existing Array.isArray guard. Valid missing-capability traces still produce deterministic extension_capability_missing:<name>:<capability> warnings.

  • The .agents/pm/ changes are expected pm-managed tracker evidence for this repo; I used the stored repo learning and did not flag them.

No remaining actionable issues found in the final pm-cli head. The SDK hook context, manifest capability diagnostics, package doctor guardrails, and package-first dogfood checks all look merge-ready from this review pass.


🧠 Learnings used
Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In `unbraind/pm-cli`, the semantic/hybrid search fallback-on-any-error behavior in `src/cli/commands/search.ts` is intentional. The product requirement is that `pm search` must never block an agent. Every degradation emits a machine-readable `search_<mode>_fallback:<reason>:using_keyword_mode` warning (reason ∈ `timeout`|`connection`|`error`) returned to the caller and captured by telemetry. Sentry was explicitly rejected for this path because it is lazily initialized only in error paths and would impose async init latency on the hot (successful) search path.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-21T22:17:12.545Z
Learning: In the `unbraind/pm-cli` repository, files under `.agents/pm/` (with extensions `.toon` and `.jsonl`) are generated and managed exclusively by the `pm` CLI tool itself (via `pm create`, `pm close`, `pm update`, etc.). They are never manually edited. Committing these files alongside the work they track is the established convention in this repo (see PRs `#36`–#39). These files should NOT be flagged with "do not edit directly" or similar warnings in code review.

Learnt from: unbraind
Repo: unbraind/pm-cli PR: 0
File: :0-0
Timestamp: 2026-05-22T21:06:50.101Z
Learning: In the pm-cli project, `history-redact`, `restore`, and `history-repair` all follow the same intentional read-compute-lock-write pattern: read history + item document outside the lock → compute the rewritten content → acquire the per-item lock → atomic write with rollback. Tightening the read inside the lock is acknowledged as a future hardening item but is intentionally deferred so the locking model stays consistent across all three sibling commands.

Learnt from: CR
Repo: unbraind/pm-cli PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-20T21:10:16.414Z
Learning: Use sandbox-safe commands for verification. For documentation-only work, run at least `pnpm build` and a focused link/content check. For broader work, run linked tests, coverage, validation, and release gates.
✅ Action performed

Full review finished.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces several opt-in enhancements to the pm-changelog extension, including structured JSON document output, within-release grouping, Conventional-Commits heading mapping, contributor lists, and release history windowing. Additionally, it enriches the SDK's onWrite hook context with item identity, snapshots, and changed fields across item mutations, and adds robust manifest capability guardrails and doctor diagnostics to identify missing capabilities during extension activation. As there are no review comments provided, I have no feedback to offer on the changes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 84b5ef7041

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

});
}
if (limit !== undefined && result.length > limit) {
result = result.slice(0, limit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the requested release count when limiting

When --all-release-tags --limit N runs while there are pending Unreleased items, resolveReleaseTagWindows prepends the Unreleased window before tagged releases, so this slice counts that window against the limit. For example --limit 1 emits only Unreleased and drops the newest tagged release, even though the new option is documented as keeping the newest N releases and the comment above says Unreleased is always kept. The limit needs to apply to tagged release windows separately from the leading Unreleased section.

Useful? React with 👍 / 👎.

}
if (!declared.has(capability)) {
throw new TypeError(
throw createRegistrationValidationError(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve missing-capability traces from nested validation

When an extension has commands but omits schema and calls registerCommand({ ..., flags }), this traced capability error is thrown from inside the surrounding registerCommand metadata try, then immediately caught and rethrown with a fresh registerCommandTrace that drops missing_capability. In that scenario activation still fails, but doctor cannot emit the new extension_capability_missing:<name>:schema remediation promised by the docs; preserve the existing registration-validation trace when wrapping nested metadata errors.

Useful? React with 👍 / 👎.

}
else {
for (const tag of tags)
push(tag, item);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Sanitize label headings before rendering

With the new --section-by label mode, tag values from --input/--stdin or hand-edited tracker data are copied directly into ### headings. If a tag contains a newline or Markdown heading text, the generated changelog can be structurally corrupted by an injected section even though item titles are escaped; normalize label headings the same way as type/status headings or escape them before rendering.

Useful? React with 👍 / 👎.

@unbraind
unbraind merged commit 555f164 into main Jun 3, 2026
13 checks passed
@unbraind
unbraind deleted the sdk-hook-context-capability-guardrails-20260603 branch June 3, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant